﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace WebServer
{
    class Program
    {
        static void Main(string[] args)
        {

            //Pdelvo.Stream
            var listener = new TcpListener(IPAddress.Any, 9999);
            listener.Start(8);
            listener.BeginAcceptSocket(AcceptClient, listener);
            while (true)
            {
                var command = Console.ReadLine();
                if (string.IsNullOrEmpty(command)) continue;
                if (command == "exit") break;
            }
            listener.Stop();
            Console.ReadLine();
        }

        private static void AcceptClient(IAsyncResult ar)
        {
            var listener = ar.AsyncState as TcpListener;
            if (listener == null) return;
            try
            {
                var socket = listener.EndAcceptSocket(ar);
                try
                {
                    var r = new Request(socket);
                    Console.WriteLine(r.Method);
                    Console.WriteLine(r.Url);
                    Console.WriteLine(r.HttpVersion);
                    foreach (var i in r.Headers)
                        Console.WriteLine("\t{0}", i);
                    var rsp = new Response(r.InputStream);
                    rsp.FlushHeader();
                    rsp.TextWriter.WriteLine("<h1>TEST</h1>");
                    rsp.FinalFlush();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
            catch (Exception)
            {
                return;
            }
            listener.BeginAcceptSocket(AcceptClient, listener);
        }
    }

    public class SocketEventArgs : EventArgs
    {
        public Socket Socket { get; private set; }
        public SocketEventArgs(Socket socket)
        {
            Socket = socket;
        }
    }
    public class Listener
    {
        public event EventHandler<SocketEventArgs> SocketAccepted;
        protected virtual void OnSocketAccepted(SocketEventArgs e)
        {
            if (SocketAccepted != null) SocketAccepted(this, e);
        }
        public IPEndPoint EndPoint { get; private set; }
        public Socket Socket { get; private set; }
        public Listener(IPEndPoint endPoint)
        {
            EndPoint = endPoint;
            Socket = new Socket(
                AddressFamily.InterNetwork,
                SocketType.Stream,
                ProtocolType.Tcp);
        }
        public Listener(int backlog)
        {
            Socket.Bind(EndPoint);
            Socket.Listen(backlog);
            Socket.BeginAccept(AcceptClient, null);
        }
        private void AcceptClient(IAsyncResult ar)
        {
            var client = Socket.EndAccept(ar);
            ThreadPool.QueueUserWorkItem(o => OnSocketAccepted(new SocketEventArgs(client)));
            Socket.BeginAccept(AcceptClient, null);
        }
    }
    public class Request
    {
        const string Space = " ";
        const string EndOfLine = "\r\n";

        public string Method;
        public string Url;
        public string HttpVersion;
        public List<string> Headers;
        public NetworkStream InputStream;

        private readonly Encoding _enc;
        private readonly List<byte> _tmp;
        private readonly List<byte> _queue;

        public Request(Socket socket)
        {
            _enc = Encoding.ASCII;
            _tmp = new List<byte>();
            _queue = new List<byte>();
            Headers = new List<string>();
            InputStream = new NetworkStream(socket);

            if (!TryParseTo(InputStream, Space, out Method)) throw new ArgumentException("invalid request (Method)");
            if (!TryParseTo(InputStream, Space, out Url)) throw new ArgumentException("invalid request (Url)");
            if (!TryParseTo(InputStream, EndOfLine, out HttpVersion)) throw new ArgumentException("invalid request (HttpVersion)");

            while (true)
            {
                string line;
                if (!TryParseTo(InputStream, EndOfLine, out line))
                    throw new ArgumentException("invalid request (Headers)"); ;
                if (string.IsNullOrEmpty(line)) break;
                Headers.Add(line);
            }

        }
        private bool TryParseTo(Stream stream, string parseTo, out string value)
        {
            _tmp.Clear();
            _queue.Clear();
            value = null;
            while (true)
            {
                var bb = stream.ReadByte();
                if (bb < 0) return false;
                var b = (byte) bb;
                _queue.Add(b);
                if (_queue.Count < parseTo.Length) continue;
                var match = true;
                for (var i = 0; i < parseTo.Length; i++)
                {
                    if (_queue[i] == parseTo[i]) continue;
                    match = false;
                    break;
                }
                if (!match)
                {
                    _tmp.Add(_queue[0]);
                    _queue.RemoveAt(0);
                    continue;
                }
                value = _enc.GetString(_tmp.ToArray());
                return true;
            }
        }
    }

    public class Response
    {
        public int ResponseCode { get; set; }
        public string ResponseCodeName { get; set; }
        public string HttpVersion { get; set; }
        public string HttpServerName { get; set; }
        public string ContentType { get; set; }
        public StringDictionary Header { get; set; }

        public TextWriter TextWriter { get; private set; }
        public Stream ResponseStream { get; private set; }

        internal Response(Stream responseStream)
        {
            ResponseCode = 200;
            ContentType = "text/html";
            HttpVersion = "HTTP/1.1";

            ResponseStream = responseStream;
            TextWriter = new StreamWriter(responseStream);
            Header = new StringDictionary();
        }

        public void FlushHeader()
        {
            TextWriter.WriteLine(HttpVersion + " " + ResponseCode + " " + ResponseCodeName);
            TextWriter.WriteLine("Date: " + DateTime.Now.ToUniversalTime().ToString("r"));
            TextWriter.WriteLine("Content-Type: " + ContentType);

            foreach (DictionaryEntry item in Header)
            {
                TextWriter.WriteLine(item.Key + ": " + item.Value);
            }
            TextWriter.WriteLine();
            TextWriter.Flush();
        }

        public void FinalFlush()
        {
            TextWriter.Flush();
            ResponseStream.Flush();
            ResponseStream.Close();
        }
    }

}
